﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;

namespace IcqSharp
{
    public enum RateLevel : byte
    {
        Clear = 0x01,
        Alert = 0x02,
        Limit = 0x03,
        Idle10 = 0x10,
        Idle30 = 0x11,
        idle50 = 0x12,
        Idle70 = 0x13,
    }

    public enum RateQueueType : byte
    {
        Default  = 0,  // standard - pushes all items without much delay
        Request  = 1,  // request  - pushes only first item on duplicity
        Response = 2   // response - pushes only last item on duplicity
    }

    public class RatesGroup
    {
        public UInt32 WindowSize;
        public UInt32 ClearLevel;
        public UInt32 AlertLevel;
        public UInt32 LimitLevel;
        public UInt32 MaxLevel;

        // current level
        public Int32 rCurrentLevel;
        public Int32 tCurrentLevel;

        // links
        public List<Tuple<UInt16, UInt16>> Pairs;
    }


    public static class Rates
    {
        private static List<RatesGroup> groups = new List<RatesGroup>();

        public static void Initialize(MemoryStream stream)
        {
            // parse rate data block
            var count = stream.ReadUInt16();

            // parse group details
            for (int i = 0; i < count; i++)
            {
                var rg = new RatesGroup();
                if (stream.Available() >= 35)
                {
                    stream.Seek(+2, SeekOrigin.Current); // group id
                    rg.WindowSize = stream.ReadUInt32();
                    rg.ClearLevel = stream.ReadUInt32();
                    rg.AlertLevel = stream.ReadUInt32();
                    rg.LimitLevel = stream.ReadUInt32();
                    stream.Seek(+8, SeekOrigin.Current);
                    rg.MaxLevel = stream.ReadUInt32();
                    stream.Seek(+5, SeekOrigin.Current);
                }
                else
                { 
                    // packet broken, put some basic defaults
                    rg.WindowSize = 10;
                    rg.MaxLevel = 5000;
                }
                rg.rCurrentLevel = (Int32)rg.MaxLevel;

                groups.Add(rg);
            }

            // parse group associated pairs
            for (int i = 0; i < count; i++)
            {
                var rg = groups[i];

                if (stream.Available() < 4) break;

                stream.Seek(+2, SeekOrigin.Current); // group id
                var num = stream.ReadUInt16();

                if (stream.Available() < num * 4) break;

                rg.Pairs = new List<Tuple<UInt16,UInt16>>();
                for (int n = 0; n < num; n++)
                    rg.Pairs.Add(new Tuple<UInt16, UInt16>(
                        stream.ReadUInt16(), stream.ReadUInt16()));
            }
        }

        public static UInt16 GetGroupFromSNAC(UInt16 Family, UInt16 Command)
        {
            for (var i = 0; i < groups.Count; i++)
            {
                var gr = groups[i];

                var found = gr.Pairs.SingleOrDefault(t => 
                    t.Item1 == Family && t.Item2 == Command);

                if (found != null) return (UInt16)(i + 1);
            }

            return 0;
        }
        public static UInt16 GetGroupFromPacket(Packet p)
        {
            if (p.Channel == Channel.Data && p.Data.Length >= 0x10)
            {
                p.Data.Seek(+6, SeekOrigin.Begin);

                var family = p.Data.ReadUInt16();
                var command = p.Data.ReadUInt16();

                return GetGroupFromSNAC(family, command);
            }
     
            return 0;
        }


        public static RatesGroup GetGroup(UInt16 Group)
        {
            if (Group != 0 && Group <= groups.Count)
                return groups[Group - 1];

            return null;
        }


        public static Int32 GetNextRateLevel(UInt16 Group)
        {
            var rg = GetGroup(Group);
            if (rg == null) return -1;
        
            Int32 nLevel = (Int32)(rg.rCurrentLevel * (rg.WindowSize - 1) / rg.WindowSize + 
                (DateTime.Now.Ticks - rg.tCurrentLevel) / rg.WindowSize);

            return nLevel < (int)rg.MaxLevel ? nLevel : (Int32)rg.MaxLevel;
        }


        public static Int32 GetDelayToLimitLevel(UInt16 Group, Int32 Level)
        {
            var rg = GetGroup(Group);
            if (rg == null) return 0;

            return (Int32)((GetLimitLevel(Group, Level) - rg.rCurrentLevel) * rg.WindowSize + rg.rCurrentLevel);
        }


        public static void packetSent(Packet p)
        {
            var Group = GetGroupFromPacket(p);
            if (Group != 0) UpdateLevel(Group, GetNextRateLevel(Group));
        }


        public static void UpdateLevel(UInt16 Group, Int32 Level)
        {
            var rg = GetGroup(Group);
            if (rg == null) return;

            rg.rCurrentLevel = Level;
            rg.tCurrentLevel = (Int32)DateTime.Now.Ticks;
    
            // Rates: New level %d for #%d", nLevel, wGroup);
        }


        public static Int32 GetLimitLevel(UInt16 Group, Int32 Level)
        {
            var rg = GetGroup(Group);
            if (rg == null) return 9999;

            switch (Level)
            {
                case (Int32)RateLevel.Clear:  return (Int32)rg.ClearLevel;
                case (Int32)RateLevel.Alert:  return (Int32)rg.AlertLevel;
                case (Int32)RateLevel.Limit:  return (Int32)rg.LimitLevel;
                case (Int32)RateLevel.Idle10: return (Int32)(rg.ClearLevel + ((rg.MaxLevel - rg.ClearLevel)/10));
                case (Int32)RateLevel.Idle30: return (Int32)(rg.ClearLevel + (3*(rg.MaxLevel - rg.ClearLevel)/10));
                case (Int32)RateLevel.idle50: return (Int32)(rg.ClearLevel + ((rg.MaxLevel - rg.ClearLevel)/2));
                case (Int32)RateLevel.Idle70: return (Int32)(rg.ClearLevel + (7*(rg.MaxLevel - rg.ClearLevel)/10));
                default: return 9999;
            }
        }


        public static Packet InitAckPacket()
        {
            var p = new Packet(Channel.Data);
            p.WriteFNACHeader((UInt16)Family.Service, 
                (UInt16)ServiceFamilySubtype.ClientRateAck);

            for (UInt16 g = 1; g <= groups.Count; g++)
                p.Data.Write(g);

            return p;
        }




    }

        

    public class RatesQueueItem
    {
        public Boolean Created;
        public UInt16 Group;

        public UInt32 Contact;
        public UInt32 Uin;
        public String Uid;

        public Boolean IsEqual(RatesQueueItem other)
        {
            // the same event (equal address of _vftable) for the same contact
            return (other.Contact == Contact);
        }

        public RatesQueueItem(UInt16 Group)
        {
            this.Group = Group;
            this.Created = false;
        }
        public RatesQueueItem(RatesQueueItem original)
        {
            this.Group = original.Group;
            this.Contact = original.Contact;
            this.Uin = original.Uin;
            this.Uid = original.Uid;
            this.Created = true;
        }

        public void Execute()
        {
            // Rates: Error executing abstract event.
            Debugger.Break();
        }

        public Boolean IsOverRate(Int32 Level)
        {
            //icq_lock l(ppro->m_ratesMutex);

            return Rates.GetNextRateLevel(Group) <
                    Rates.GetLimitLevel(Group, Level);
        }

    }

    public delegate void IcqRateFunc();

    public class RateDelayArgs
    {
        public Int32 Delay;
        public RatesQueue Queue;
        public IcqRateFunc DelayCode;
    }


    // generic item queue (FIFO)
    public class RatesQueue
    {
        private String Descr;
  
        //icq_critical_section *listsMutex;  // we need to be thread safe
        //int pendingListSize;
    
        private List<RatesQueueItem> Pending;
        private Int32 Duplicates;

        private void CleanUp()
        {
            Pending.Clear();
            Pending = null;
        }
        private void ProcessQueue()
        {
            if (Pending == null) return;

            if (!Session.IcqOnline) {
                CleanUp(); return; }

            // take from queue, execute
            var item = Pending[0];

            //ppro->m_ratesMutex->Enter();
            //listsMutex->Enter();
                
            if (item.IsOverRate(LimitLevel))
            { 
                // the rate is higher, keep sleeping
                int Delay = Rates.GetDelayToLimitLevel(item.Group,
                    Rates.GetLimitLevel(item.Group, WaitLevel));

                //listsMutex->Leave();
                //ppro->m_ratesMutex->Leave();
                if (Delay < 10) Delay = 10;
                InitDelay(Delay, ProcessQueue);
                return;
            }
            //ppro->m_ratesMutex->Leave();

            if (Pending.Count > 1)
            {
                // we need to keep order
                Pending.RemoveAt(0);
                //memmove(&pendingList[0], &pendingList[1], (pendingListSize - 1) * sizeof(rates_queue_item*));
            }
            else
                Pending = null;
                //SAFE_FREE((void**)&pendingList);

            //listsMutex->Leave();

            if (Session.IcqOnline)
            {
                // Rates: Resuming %s., Descr
                item.Execute();
            }
            else
                ; // Rates: Discarding %s., Descr

            if (Pending.Count != 0)
            {
                // in queue remained some items, setup timer
                //ppro->m_ratesMutex->Enter();
                int nDelay = Rates.GetDelayToLimitLevel(item.Group, WaitLevel);
                //ppro->m_ratesMutex->Leave();

                if (nDelay < 10) nDelay = 10;
                InitDelay(nDelay, ProcessQueue);
            }
        }
  
        private void RateDelayThread(Object args)
        {
            var Args = args as RateDelayArgs;

            Thread.Sleep(Args.Delay);
            Args.DelayCode();
        }

        private void InitDelay(Int32 Delay, IcqRateFunc DelayCode)
        {
            // Rates: Delay %dms, Delay

            // This will be freed in the new thread
            var Args = new RateDelayArgs() {
                Queue = this, Delay = Delay,
                DelayCode = DelayCode };

            var PTS = new ParameterizedThreadStart(RateDelayThread);
            new Thread(PTS).Start(Args);
        }

        public RatesQueue(String Descr, Int32 LimitLevel, Int32 WaitLevel, Int32 Duplicates)
        {
            //this->listsMutex = new icq_critical_section();
            //this->ppro = ppro;

            this.Descr = Descr;
            this.LimitLevel = LimitLevel;
            this.WaitLevel = WaitLevel;
            this.Duplicates = Duplicates;
        }


        void putItem(RatesQueueItem Item, Int32 MinDelay)
        {
            var Found = false;

            if (!Session.IcqOnline) return;

            // Rates: Delaying 'Descr'

            //listsMutex->Enter();
    
                if (Pending.Count > 0)
            {
                for (int i = 0; i < Pending.Count; i++)
                {
                    if (Pending[i].IsEqual(Item))
                    { 
                        if (Duplicates == -1)
                        { 
                            // discard existing, append new item
                            Pending.Remove(Pending[i]);
                            Found = true;
                        }
                        else if (Duplicates == 1)
                        { 
                            // keep existing, ignore new
                            //listsMutex->Leave();
                            return;
                        }
                        // otherwise keep existing and append new
                    }
                }
            }

            if (!Found)
            { 
                // not found, enlarge the queue
                //pendingListSize++;
                //pendingList = (rates_queue_item**)SAFE_REALLOC(pendingList, pendingListSize * sizeof(rates_queue_item*));
            }
            Pending.Add(new RatesQueueItem(Item));

            if (Pending.Count == 1)
            { 
                // queue was empty setup timer
                //listsMutex->Leave();
                //ppro->m_ratesMutex->Enter();
                var Delay = Rates.GetDelayToLimitLevel(Item.Group, WaitLevel);
                //ppro->m_ratesMutex->Leave();

                if (Delay < 10) Delay = 10;
                if (Delay < MinDelay) Delay = MinDelay;
                InitDelay(Delay, ProcessQueue);
            }
            else
                ; //listsMutex->Leave();
        }


        public Int32 LimitLevel; // RML_*
        public Int32 WaitLevel;
    }


    
}
